/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */

package fr.ove.openmath.mathematica;

/**
 *
 * @author Mr.Sam
 */
import javax.swing.*;
import javax.swing.event.*;
import java.awt.*;
import java.util.*;
import fr.ove.openmath.mathematica.*;
import fr.ove.utils.*;
import java.io.*;
import fr.inria.openmath.omapi.*;
import fr.inria.openmath.omapi.implementation.*;
import fr.ove.openmath.mathematica.omparser.PipeParserHandler;

/**
* A work book.
* Is intended to be used to access different inner frames in a Desktop by means
* labelled tabs.
*/
public class WorkBook extends JComponent implements InternalFrameListener, Savable {
    /**
    * The tabbed pane to acces the different elements
    */
    private JTabbedPane tabbedPane;
    
    /**
    * The Desktop.
    */
    private Desktop desktop;
    
    /**
    * Indicates if the instance need to be saved.
    */
    private boolean needed = true;
    
    /**
     * The list of listeners of the instance.
     */
    private EventListenerList listeners = new EventListenerList();
    
    /**
     * Only one ChangeEvent is needed per WorkBook instance since the
     * event's only (read-only) state is the source property.  The source
     * of events generated here is always "this".
     */
    private transient ChangeEvent changeEvent = null;
    
    /**
    * A connection factory.
    */
    ConnectionFactory factory;
    
    /**
    * A file chooser
    */
    private WorkSheetFileChooser chooser;
    
    /**
    * A status bar
    */
    private StatusBar statusBar;
    
    /**
    * The Constructor.
    */
    public WorkBook(ConnectionFactory factory, StatusBar statusBar) {
        setLayout(new BorderLayout());
        desktop = new Desktop();
        
        chooser = new WorkSheetFileChooser(this);
        this.factory = factory;
        this.statusBar = statusBar;
        addChangeListener(statusBar);
        
        add(desktop, BorderLayout.CENTER);
        tabbedPane = new JTabbedPane(JTabbedPane.BOTTOM);
        tabbedPane.addChangeListener(
            new ChangeListener() {
                public void stateChanged(ChangeEvent ce) {
                    JDesktopPaneWrapper comp = (JDesktopPaneWrapper) tabbedPane.getSelectedComponent();
                    if (comp != null) {
                        if (comp.getComponentCount() == 0)
                            comp.setJDesktopPane();
                        
                        int index = tabbedPane.getSelectedIndex();
                        String title = tabbedPane.getTitleAt(index);
                        desktop.setSelected(title);
                    }
                }
            }
        );
    }
    
    /**
    * Adds a new tab (i.e. a new inner frame into the Desktop) to the WorkBook.
    */
    public void addTab() {
        if (getComponent(0) == desktop) {
            remove(desktop);
            add(tabbedPane, BorderLayout.CENTER);
        }
        
        JInternalFrame jif = desktop.addNewInternalFrame(new OMWorkSheet(this, factory));
        jif.addInternalFrameListener(this);
        jif.addVetoableChangeListener(chooser);
        tabbedPane.addTab(jif.getTitle(), new JDesktopPaneWrapper(desktop));
        tabbedPane.setSelectedIndex(tabbedPane.getTabCount() - 1);
        fireStateChanged();
        validate();
    }
    
    /**
    * Adds a new palette to the desktop.
    * @param paletteIdentifier the palette identfier.
    * @param contentPan the palette (i.e. the content pane for the inner frame opened in the desktop)
    */
    public void addPalette(String paletteIdentifier, Container contentPane) {
        desktop.addNewPaletteContentPane(paletteIdentifier, contentPane);
    }
    /**
    * Shows the palette with the specified name identfier.
    * @param identifier the palette identifier.
    */
    public void showPalette(String identifier) {
        desktop.showPalette(identifier);
    }
    
    /**
    * Removes the tab with the specified name.
    * @param name the name of the tab.
    */
    public void removeTab(String name) {
        JInternalFrame jif = desktop.removeInternalFrame(name);
        if (jif != null) {
            jif.removeInternalFrameListener(this);
            int index = tabbedPane.indexOfTab(name);
            tabbedPane.removeTabAt(index);
            fireStateChanged();
        }
    }
    
    /**
    * Returns the active inner frame in the work book.
    */
    public JInternalFrame getActiveInternalFrame() {
        return desktop.getActiveInternalFrame();
    }
    
    /**
    * Returns the number of tab (i.e. inner frames) in the instance.
    */
    public int getTabCount() {
        return tabbedPane.getTabCount();
    }
    /**
     * Adds a ChangeListener to the instance.
     * @param l the ChangeListener to add
     * @see #fireStateChanged
     * @see #removeChangeListener
     */
    public void addChangeListener(ChangeListener l) {
        listeners.add(ChangeListener.class, l);
    }
    
    /**
    * Cascade the frame in the desktop.
    */
    public void cascade() {
        desktop.cascade();
    }
    
    /**
    * Closes all the frames in the desktop.
    */
    public void closeAll() {
        desktop.closeAll();
    }
    
    /**
     * Removes a ChangeListener from the instance.
     * @param l the ChangeListener to remove
     * @see #fireStateChanged
     * @see #addChangeListener
     */
    public void removeChangeListener(ChangeListener l) {
        listeners.remove(ChangeListener.class, l);
    }
    
    /**
     * Send a ChangeEvent, whose source is this tabbedpane, to
     * each listener.  This method method is called each time 
     * a ChangeEvent is received from the model.
     * @see #addChangeListener
     * @see EventListenerList
     */
    protected void fireStateChanged() {
        // Guaranteed to return a non-null array
        Object[] tabListeners = listeners.getListenerList();
        // Process the listeners last to first, notifying
        // those that are interested in this event
        for (int i = tabListeners.length - 2; i >= 0; i -= 2) {
            if (tabListeners[i] == ChangeListener.class) {
                // Lazily create the event:
                if (changeEvent == null)
                    changeEvent = new ChangeEvent(this);
                    
                ((ChangeListener) tabListeners[i+1]).stateChanged(changeEvent);
            }          
        }
    }
    
    /**
    * Returns the status bar.
    */
    public StatusBar getStatusBar() {
        return statusBar;
    }
    
    /**
    * Returns the icon with the specified image filename.
    * @param filename the image filename
    */
    private ImageIcon getIcon(String filename) {
        ImageIcon icon = null;
        
        java.net.URL iconURL = ClassLoader.getSystemResource(filename);
        if (iconURL != null)
            icon = new ImageIcon(iconURL);
            
        return icon;
    }
    
    
    
    // ######  InternalFrameListener interface implementation
    
    public void internalFrameActivated(InternalFrameEvent e) {
        JInternalFrame src = (JInternalFrame) e.getSource();
        int index = tabbedPane.indexOfTab(src.getTitle());
        tabbedPane.setSelectedIndex(index);
        fireStateChanged();
    }
    
    public void internalFrameClosed(InternalFrameEvent e) {
        JInternalFrame src = (JInternalFrame) e.getSource();
        String name = src.getTitle();
        
        removeTab(name);
        JDesktopPaneWrapper wrapper = (JDesktopPaneWrapper) tabbedPane.getSelectedComponent();
        if (wrapper != null) {
            wrapper.setJDesktopPane();
            int index = tabbedPane.getSelectedIndex();
            name = tabbedPane.getTitleAt(index);
            desktop.setSelected(name);
        }
        else {
            // Possible that src was the last inner frame.
            // If so, remove the tabbed pane and put the desktop itself.
            //JInternalFrame[] frames = desktop.getAllFramesInLayer(JLayeredPane.DEFAULT_LAYER.intValue());
            //if (frames.length == 1) {
                remove(tabbedPane);
                add(desktop, BorderLayout.CENTER);
            //}
        }
        fireStateChanged();
    }

    public void internalFrameClosing(InternalFrameEvent e) {
    }

    public void internalFrameDeactivated(InternalFrameEvent e) {
        fireStateChanged();
    }

    public void internalFrameDeiconified(InternalFrameEvent e) {
    }

    public void internalFrameIconified(InternalFrameEvent e) {
    }

    public void internalFrameOpened(InternalFrameEvent e) {
    }
    
    // ### Savable interface implementation
        
    /**
    * Saves the instance.
    * @returns the result of the save action. Should be <CODE>null</CODE> if nothing
    * is expected after the save operation. The saved object otherwise.
    * The instance is saved as the concatenation of all the OpenMath object request contained.
    */
    public Object save() {
        MyJInternalFrame jif = (MyJInternalFrame) desktop.getActiveInternalFrame();
        if (jif.isSaveNeeded()) {
            File file;
            String title = jif.getTitle();
            
            statusBar.setStatus("Save " + title,
                                getIcon("fr/ove/openmath/mathematica/images/save.gif")
            );
            
            
            if (title.startsWith("Untitled")) {
                int state = chooser.showSave();
                file = chooser.getSelectedFile();
                if ((file != null) && (state == JFileChooser.APPROVE_OPTION)) {
                    statusBar.setStatus("Please wait while saving the work sheet...",
                                        getIcon("fr/ove/openmath/mathematica/images/sablier.gif")
                    );
                    
                    jif.save(file);
                    setSaveNeeded(false);
                    
                    try {
                        desktop.rename(title, file.getName());
                    }
                    catch (Exception e) {
                    }
                    
                    tabbedPane.setTitleAt(tabbedPane.getSelectedIndex(), file.getName());
                    
                    statusBar.setStatus("", getIcon("fr/ove/openmath/mathematica/images/worksheet.gif"));
                }
            }
            else {
                statusBar.setStatus("Please wait while saving the work sheet...",
                                    getIcon("fr/ove/openmath/mathematica/images/sablier.gif")
                );
                    
                file = new File(title);
                jif.save(file);
                setSaveNeeded(false);
                
                statusBar.setStatus("", getIcon("fr/ove/openmath/mathematica/images/worksheet.gif"));
            }
        }
        
        return null;
    }
    
    /**
    * Saves the instance into the specified file.
    * @returns the result of the save action. Should be <CODE>null</CODE> if nothing
    * is expected after the save operation. The saved object otherwise.
    */
    public Object save(File file) {
        return null;
    }
        
    /**
    * Saves the instance with the specified name.
    * @param name the specified name.
    * @returns the result of the save action. Should be <CODE>null</CODE> if nothing
    * is expected after the save operation. The saved object otherwise.
    */
    public Object saveAs(String name) {
        return null;
    }
        
    /**
    * Checks if the instance need to be saved.
    * @return <CODE>true</CODE> if the instance need to be saved. <CODE>false</CODE> otherwise.
    */
    public boolean isSaveNeeded() {
        return needed;
    }
        
    /**
    * Sets if the instance need to be saved.
    * @param needed <CODE>true</CODE> if the instance need to be saved. <CODE>false</CODE> otherwise.
    */
    public void setSaveNeeded(boolean needed) {
        this.needed = needed;
    }
    
    /**
    * Opens a file.
    */
    public void open() {
        statusBar.setStatus("Select a work sheet to open...",
                            getIcon("fr/ove/openmath/mathematica/images/open.gif")
        );
        
        int state = chooser.showOpen();
        File file = chooser.getSelectedFile();
        if ((file != null) && (state == JFileChooser.APPROVE_OPTION)) {
            statusBar.setStatus("Please wait while opening the work sheet...",
                                getIcon("fr/ove/openmath/mathematica/images/sablier.gif")
            );
            
            PipeParserHandler handler = new PipeParserHandler();
            XMLParser parser = new XMLParser(handler);
            
            Vector elems = new Vector(); 
            try {
                FileInputStream fis = new FileInputStream(file);
                try {
                    String parsed;
                    while (fis.available() != 0) {
                        parser.initParse(fis);
                        parser.parseObject(fis);
                        
                        parsed = handler.getOmObjectParsed();
                        
                        if ((parsed != null) && (!parsed.equals("")))
                            elems.addElement(parsed);
                    }
                    fis.close();
                    
                    addTab();
                    MyJInternalFrame jif = (MyJInternalFrame) desktop.getActiveInternalFrame();
                    try {
                        desktop.rename(jif.getTitle(), file.getName());
                    }
                    catch (Exception e) {
                    }
                    tabbedPane.setTitleAt(tabbedPane.getSelectedIndex(), file.getName());

                    if (elems.size() != 0) {
                        OMWorkSheet worksheet = (OMWorkSheet) jif.getContentPane();
                        boolean addNewElement = false;
                        for (Enumeration e = elems.elements(); e.hasMoreElements(); ) {
                            if (addNewElement)
                                worksheet.addNewElement();
                            worksheet.getCurrentElement().setOpenMathRequest((String) e.nextElement());
                            addNewElement = true;
                        }
                        jif.setSaveNeeded(false);
                        worksheet.setSaveNeeded(false);
                    }
                    
                    statusBar.setStatus("", getIcon("fr/ove/openmath/mathematica/images/worksheet.gif"));
                    
                }
                catch (IOException ioe) {
                    statusBar.setStatus("Something wrong occurs while reading the worksheet.", 
                                        getIcon("fr/ove/openmath/mathematica/images/oups.gif"));
                }
            }
            catch (FileNotFoundException fnfe) {
                statusBar.setStatus("Couldn't find the specified the worksheet.", 
                                    getIcon("fr/ove/openmath/mathematica/images/oups.gif"));
            }
        }
        else {
            if (getTabCount() != 0)
                statusBar.setStatus("", getIcon("fr/ove/openmath/mathematica/images/worksheet.gif"));
            else
                statusBar.clear();
        }
    }
}